﻿
using Boilen.Primitives.Members;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Text;


namespace Boilen.Primitives {

    /// <summary>
    /// Contains utility methods for performing common operations.
    /// </summary>
    public static class Util {

        /// <summary>
        /// Capitalizes the specified string.
        /// </summary>
        /// <param name="value">The string to be capitalized.</param>
        /// <returns>The original string with the first character capitalized.</returns>
        public static string Capitalize( string value ) {
            Ensure.NotNullOrEmpty( value );
            return char.ToUpperInvariant( value[0] ) + value.Substring( 1 );
        }

        /// <summary>
        /// Lowercases the specified string.
        /// </summary>
        /// <param name="value">The string to be lowercased.</param>
        /// <returns>The original string with the first character lowercased.</returns>
        public static string Lowercase( string value ) {
            Ensure.NotNullOrEmpty( value );
            return value.Length > 1 && char.IsUpper( value, 1 )
                ? value // Don't lowercase acronyms.
                : char.ToLowerInvariant( value[0] ) + value.Substring( 1 );
        }


        /// <summary>
        /// Enumerates a linked collection of items.
        /// </summary>
        /// <typeparam name="T">The type of the item to enumerate.</typeparam>
        /// <param name="start">The item to enumerate.</param>
        /// <param name="next">The function used to return the next item.</param>
        /// <returns>The linked collection of items.</returns>
        public static IEnumerable<T> Enumerate<T>( T start, Func<T, T> next )
            where T : class {
            next = next ?? (_ => null);
            for( T item = start; item != null; item = next( item ) )
                yield return item;
        }


        /// <summary>
        /// Joins the collection of strings with the specified separator.
        /// </summary>
        /// <param name="collection">The strings to join.</param>
        /// <param name="separator">The separator to join the strings with.</param>
        /// <returns>A string containing each string in <paramref name="collection"/> separated by <paramref name="separator"/>.</returns>
        public static string Join( IEnumerable<string> collection, string separator ) {
            return Util.Join( collection, separator, ( i, s ) => s );
        }

        /// <summary>
        /// Joins the collection of objects with the specified separator after converting each object to a string.
        /// </summary>
        /// <typeparam name="T">The type of objects to join.</typeparam>
        /// <param name="collection">The objects to join.</param>
        /// <param name="separator">The separator to join the string representations of the objects.</param>
        /// <param name="toString">A method for converting an object to a string.</param>
        /// <returns>A string containing the string representation returned by <paramref name="toString"/> of each object in <paramref name="collection"/> separated by <paramref name="separator"/>.</returns>
        public static string Join<T>( IEnumerable<T> collection, string separator, Func<int, T, string> toString ) {
            return Util.Join( collection, ( i, last ) => separator, toString );
        }

        /// <summary>
        /// Joins the collection of objects using the separator returned for each join after converting each object to a string.
        /// </summary>
        /// <typeparam name="T">The type of objects to join.</typeparam>
        /// <param name="collection">The objects to join.</param>
        /// <param name="separator">A method for generating a string separator to join two objects, either in the middle or at the end of the collection.</param>
        /// <param name="toString">A method for converting an object to a string.</param>
        /// <returns>A string containing the string representation returned by <paramref name="toString"/> of each object in <paramref name="collection"/> separated by the strings returned by <paramref name="separator"/>.</returns>
        public static string Join<T>( IEnumerable<T> collection, Func<int, bool, string> separator, Func<int, T, string> toString ) {
            Ensure.NotNull( collection, separator, toString );

            var sb = new StringBuilder( );
            Util.Iterate(
                collection,
                ( i, last ) => sb.Append( separator( i, last ) ),
                ( i, item ) => sb.Append( toString( i, item ) )
            );

            return sb.ToString( );
        }


        /// <summary>
        /// Iterates over a collection of objects, performing an operation on and separating each object.
        /// </summary>
        /// <typeparam name="T">The type of objects to iterate over.</typeparam>
        /// <param name="collection">The objects to iterate over.</param>
        /// <param name="separate">An action to perform between each pair of objects, either in the middle or at the end of the collection.</param>
        /// <param name="operate">An action to perform on each object.</param>
        public static void Iterate<T>( IEnumerable<T> collection, Action<int, bool> separate, Action<int, T> operate ) {
            Ensure.NotNull( collection, separate, operate );

            T[] items = collection.ToArray( );
            for( int i = 0; i < items.Length; ++i ) {
                if( i > 0 ) {
                    bool isLastSeparator = (i == items.Length - 1);
                    separate( i, isLastSeparator );
                }

                T item = items[i];
                operate( i, item );
            }
        }


        /// <summary>
        /// Retrieves the first attribute of the specified type on the member, or <null/> if the attribute could not be found.
        /// </summary>
        /// <typeparam name="T">The type of attribute to look for.</typeparam>
        /// <param name="member">The member with the desired attribute.</param>
        /// <returns>The first attribute of type <typeparamref name="T"/> declared on <paramref name="member"/>, or <null/> if no attributes of type <typeparamref name="T"/> where found.</returns>
        public static T GetAttribute<T>( MemberInfo member )
            where T : Attribute {
            Ensure.NotNull( member );

            return member.GetCustomAttributes( typeof( T ), true )
                .Cast<T>( )
                .FirstOrDefault( );
        }


        /// <summary>
        /// Gets the aggregate set of <see cref="Extensions"/> used by all of the specified members.
        /// </summary>
        /// <param name="members">The collection of <see cref="Member"/> objects to examine.</param>
        /// <returns>The set of <see cref="Extensions"/> used by all <paramref name="members"/>.</returns>
        public static Extensions GetUsedExtensions( IEnumerable<Member> members ) {
            return members
                .Where( m => m != null )
                .Aggregate( Extensions.None, ( e, m ) => e | m.UsedExtensions );
        }

        /// <summary>
        /// Gets the aggregate set of <see cref="Extensions"/> used by all of the specified members.
        /// </summary>
        /// <param name="members">The collection of <see cref="Member"/> objects to examine.</param>
        /// <returns>The set of <see cref="Extensions"/> used by all <paramref name="members"/>.</returns>
        public static Extensions GetUsedExtensions( params Member[] members ) {
            return GetUsedExtensions( members.AsEnumerable( ) );
        }

    }

}
